<?php

namespace Wentaophp\Yii2Aop;

use Composer\ClassMapGenerator\PhpFileParser;
use helper\Annotations\TransactionAspect;
use Symfony\Component\Finder\Finder;
use Wentaophp\Yii2Aop\Aop\Annotation\AnnotationInterface;
use Wentaophp\Yii2Aop\Aop\Annotation\AnnotationReader;
use Wentaophp\Yii2Aop\Aop\Annotation\Aspect;
use Wentaophp\Yii2Aop\Aop\Annotation\AspectLoader;
use Wentaophp\Yii2Aop\Aop\AspectManager;
use Wentaophp\Yii2Aop\Aop\Ast;
use Wentaophp\Yii2Aop\Aop\AstVisitorRegistry;
use Wentaophp\Yii2Aop\Aop\Collector\AnnotationCollector;
use Wentaophp\Yii2Aop\Aop\Collector\AspectCollector;
use Wentaophp\Yii2Aop\Aop\Composer\ClassLoader;
use Wentaophp\Yii2Aop\Aop\ProxyCallVisitor;
use Wentaophp\Yii2Aop\Aop\ReflectionManager;
use yii\base\Component;
use yii\caching\FileCache;

class ProxyServiceProvider extends Component
{

    // 代理总缓存的key
    public const CACHE_PROXY_SERVICE_PROVIDER = 'CACHE_PROXY_SERVICE_PROVIDER_';
    // 注解收集器缓存
    private const ANNOTATION_COLLECTOR = self::CACHE_PROXY_SERVICE_PROVIDER . 'AnnotationCollector';
    // 切面收集器容器缓存
    private const ASPECT_COLLECTOR_CONTAINER = self::CACHE_PROXY_SERVICE_PROVIDER . 'AspectCollector::Container';
    // 切面规则缓存
    private const ASPECT_COLLECTOR_ASPECT_RULES = self::CACHE_PROXY_SERVICE_PROVIDER . 'AspectCollector::AspectRules';
    // 切面管理器缓存
    private const ASPECT_MANAGER = self::CACHE_PROXY_SERVICE_PROVIDER . 'AspectManager';
    // 类映射缓存
    private const CLASSMAP = self::CACHE_PROXY_SERVICE_PROVIDER . 'ClassMap';

    private string $cachePath;

    public function __construct()
    {
        $this->cachePath = \Yii::$app->getVendorPath() . DIRECTORY_SEPARATOR . 'aop-proxy' . DIRECTORY_SEPARATOR;
    }

    /**
     * @return void
     */
    public function webInit(): void
    {
        $classMap = $this->loadFormCache();
        ClassLoader::findClassLoader()->addClassMap($classMap);
        \Yii::$classMap = array_merge(\Yii::$classMap, $classMap);
    }

    /**
     * @return array
     */
    private function loadClassMap(): array
    {
        $cachedProxyFiles = File::allFiles($this->cachePath);
        $classMap = [];
        foreach ($cachedProxyFiles as $cachedProxyFile) {
            $classes = PhpFileParser::findClasses($cachedProxyFile);
            foreach ($classes as $class) {
                $classMap[$class] = $cachedProxyFile->getPathname();
            }
        }
        return $classMap;
    }

    /**
     * 缓存注解, 以及切面相关数据
     * @param array $canningPath
     * @return array
     * @throws \Exception
     */
    public function loadAnnotationAndAspect(array $canningPath): array
    {
        AstVisitorRegistry::insert(ProxyCallVisitor::class);

        $cachePath = $this->cachePath;
        File::cleanDirectory($cachePath);
        File::makeDirectory($cachePath, 0777, true, true);

        $basePath = \Yii::$app->getBasePath();
        $reader = new AnnotationReader();
        $classes = ReflectionManager::getAllClasses($canningPath);
        foreach ($classes as $className => $reflectionClass) {
            $this->collectAnnotations($reader, $reflectionClass, $className);
        }
        // 包含注解的类
        $annotationClasses = array_keys(AnnotationCollector::getContainer());
        // 切片器类
        $aspectClasses = array_keys(AspectCollector::getContainer()['classes']);
        // 不是切面类的类
        $notAspectClasses = array_diff($annotationClasses, $aspectClasses);
        foreach ($notAspectClasses as $class) {
            $code = (new Ast())->proxy($class);
            File::put($cachePath . str_replace('\\', '_', $class . '.php'), $code);
        }

        return $this->setCache();
    }

    /**
     * @return array
     */
    private function loadFormCache(): array
    {

        $cache = \Yii::createObject(FileCache::class);
        $cache->cachePath = $this->cachePath;
        if (!$cache->exists(self::CLASSMAP)) {
            return [];
        }
        AnnotationCollector::setContainer($cache->get(self::ANNOTATION_COLLECTOR));
        AspectCollector::setContainer($cache->get(self::ASPECT_COLLECTOR_CONTAINER));
        AspectCollector::setAspectRules($cache->get(self::ASPECT_COLLECTOR_ASPECT_RULES));
        AspectManager::setContainer($cache->get(self::ASPECT_MANAGER));
        return $cache->get(self::CLASSMAP);
    }

    /**
     * @return array
     */
    private function setCache(): array
    {

        $cache = \Yii::createObject(FileCache::class);
        $cache->cachePath = $this->cachePath;
        $classMap = $this->loadClassMap();
        $cache->set(self::CLASSMAP, $classMap);
        $cache->set(self::CACHE_PROXY_SERVICE_PROVIDER, 1);
        $cache->set(self::ANNOTATION_COLLECTOR, AnnotationCollector::getContainer());
        $cache->set(self::ASPECT_COLLECTOR_CONTAINER, AspectCollector::getContainer());
        $cache->set(self::ASPECT_COLLECTOR_ASPECT_RULES, AspectCollector::getAspectRules());
        $cache->set(self::ASPECT_MANAGER, AspectManager::getContainer());
        return $classMap;
    }

    /**
     * 收集可用的注解
     * @param AnnotationReader $reader
     * @param $reflection
     * @param $className
     * @return void
     * @throws \Exception
     */
    public function collectAnnotations(AnnotationReader $reader, $reflection, $className): void
    {
        $classAnnotations = $reader->getClassAnnotations($reflection);

        // 分析类批注
        if (!empty($classAnnotations)) {
            foreach ($classAnnotations as $classAnnotation) {
                if ($classAnnotation instanceof AnnotationInterface) {
                    $classAnnotation->collectClass($className);
                }

                if ($classAnnotation instanceof Aspect) {
                    $this->loadAspect($className, $classAnnotation->priority);
                }
            }
        }
        // 分析属性批注
        $properties = $reflection->getProperties();
        foreach ($properties as $property) {
            $propertyAnnotations = $reader->getPropertyAnnotations($property);
            if (!empty($propertyAnnotations)) {
                foreach ($propertyAnnotations as $propertyAnnotation) {
                    if ($propertyAnnotation instanceof AnnotationInterface) {
                        $propertyAnnotation->collectProperty($className, $property->getName());
                    }
                }
            }
        }
        // 分析方法批注
        $methods = $reflection->getMethods();
        foreach ($methods as $method) {
            $methodAnnotations = $reader->getMethodAnnotations($method);
            if (!empty($methodAnnotations)) {
                foreach ($methodAnnotations as $methodAnnotation) {
                    if ($methodAnnotation instanceof AnnotationInterface) {
                        $methodAnnotation->collectMethod($className, $method->getName());
                    }
                }
            }
        }
        unset($reflection, $classAnnotations, $properties, $methods);
    }

    public function loadAspect($aspect, $value): void
    {
        $priority = (int)$value;


        [$instanceClasses, $instanceAnnotations, $instancePriority] = AspectLoader::load($aspect);

        $classes = $instanceClasses ?: [];
        // Annotations
        $annotations = $instanceAnnotations ?: [];
        // Priority
        $priority = $priority ?: ($instancePriority ?? null);
        // Save the metadata to AspectCollector
        AspectCollector::setAround($aspect, $classes, $annotations, $priority);
    }

    public function allFiles($directory, $hidden = false): array
    {
        return iterator_to_array(
            Finder::create()->files()->ignoreDotFiles(!$hidden)->in($directory)->sortByName(),
            false
        );
    }
}
